

use crate::error::AppKind;

use std::collections::VecDeque;
use std::fmt;

trait Processor {
    fn process(&mut self) -> crate::error::Result<()>;
}



#[derive(Debug)]
enum Event {
    Lines(String),
}

impl fmt::Display for Event {
    fn fmt(&self, f:&mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Event::Lines(s) => {
                write!(f, "{}", s)
            }
        }
    }
}

#[derive(Default, Debug)]
struct Channel {
    events: VecDeque<Event>,
}
impl Channel {
    fn push(&mut self, e: Event) {
        self.events.push_back(e)
    }
    fn pop(&mut self) -> Option<Event> {
        self.events.pop_front()
    }
    #[warn(dead_code)]
    fn len(& self) -> usize {
        self.events.len()
    }
}


use std::fs::File;
use std::io::{BufReader, BufRead, Read, SeekFrom, Seek};
use std::ops::ControlFlow;

use crate::error::AppError;



#[derive(Debug)]
struct TailSource {
    fname: String, //TODO: path of file ???
    lines: u64,
}

impl Default for TailSource {
    fn default() -> Self {
        Self {
            fname: "./src/main.rs".to_owned(),
            lines: 10_u64,
        }
    }
}
impl TailSource {
    fn new(fname: &str, lines: u64) -> Self{
        Self {
            fname: fname.to_owned(),
            lines,
        }
    }

    fn read_all_lines(&self, pos: u64, channel: &mut Channel) -> std::io::Result<()>{
        let f = File::open(&self.fname)?;
        let mut reader = BufReader::new(f);
        let _ = reader.seek(SeekFrom::Start(pos))?;

        loop {
            let mut line = String::new();
            let len = reader.read_line(&mut line)? ;
            if len == 0 {
                return Ok(());
            }
            else {
                println!(".....len:{len}, line:{line}");
                channel.push(Event::Lines(line));
            }
        }
    }
    

    fn get_file_size(fname: &str) -> std::io::Result<u64> {
        std::fs::metadata(fname).map(|m| m.len())
    }

    const PAGE_SIZE: u64 = 1*1024_u64;

    // None => lines > 0
    fn rfind_lines_pos(&self, buffer: &[u8],  lines: &mut u64) -> Option<usize>{
        let res = (0..buffer.len()).rev().try_for_each(|i|  {
            if buffer[i] == 0x0a {
                *lines -= 1;
                println!("lines: {lines}, value:{i}");
                if *lines == 0 {
                    return ControlFlow::Break(i)
                }
            }
            return ControlFlow::Continue(())
        });
        if let ControlFlow::Break(i) = res {
            return Some(i);
        }
        return None;
    }
    
    fn find_position(&self) -> crate::error::Result<u64> {
        let mut end = Self::get_file_size(&self.fname)?;

        let mut reader = File::open(&self.fname)?;
        let mut buffer = vec![0_u8; (Self::PAGE_SIZE) as usize];
        let mut lines = self.lines + 1;
        println!("file size is :{end}, lines:{lines}");

        while end > 0 {
            let start = if end > Self::PAGE_SIZE {
                end - Self::PAGE_SIZE
            }
            else {
                0
            };

            let len = end - start;
            println!("hello : start:{start}, end:{end}, len:{len}");

            let seek = reader.seek(SeekFrom::Start(start))?;
            let readed = reader.read(&mut buffer[0..len as usize])?;
            println!("seek:{seek}, readed:{readed:?}");

            if lines > 0 {
                if let Some(pos) = self.rfind_lines_pos(&buffer[0..len as usize], &mut lines) {
                    return Ok(start + pos as u64 + 1);
                }
            }
            else{
                println!("lines:{lines} end");
                return Ok(end); //TODO:?
            }
        
            end = start 
        }
        Err(AppError::new_kind(AppKind::NotExistLineFeed, "Not Exist line feed in file"))
    }
    
}

impl TailSource {
    fn process(&mut self, channel: &mut Channel) -> crate::error::Result<()>{
        if self.lines > 0 {
            let pos = self.find_position()?;
            let _ = self.read_all_lines(pos, channel)?;
        }
        Ok(())
    }
}

struct TailFollow {
    fname: String,
    show_lines: u64,
    fsize: u64,
}
impl TailFollow {
    fn new(fname: &str, show_lines: u64) -> Self {
        Self {
            fname: fname.to_owned(),
            show_lines,
            fsize: 0
        }
    }
    fn get_file_size(fname: &str) -> std::io::Result<u64> {
        std::fs::metadata(fname).map(|m| m.len())
    }
    const PAGE_SIZE: u64 = 1*1024_u64;
    fn find_position(&self) -> crate::error::Result<u64> {
        let mut end = self.fsize;

        let mut reader = File::open(&self.fname)?;
        let mut buffer = vec![0_u8; (Self::PAGE_SIZE) as usize];
        let mut lines = self.show_lines + 1;
        println!("file size is :{end}, show_lines:{lines}");

        while end > 0 {
            let start = if end > Self::PAGE_SIZE {
                end - Self::PAGE_SIZE
            }
            else {
                0
            };

            let len = end - start;
            println!("hello : start:{start}, end:{end}, len:{len}");

            let seek = reader.seek(SeekFrom::Start(start))?;
            let readed = reader.read(&mut buffer[0..len as usize])?;
            println!("seek:{seek}, readed:{readed:?}");

            if lines > 0 {
                if let Some(pos) = self.rfind_lines_pos(&buffer[0..len as usize], &mut lines) {
                    return Ok(start + pos as u64 + 1);
                }
            }
            else{
                println!("lines:{lines} end");
                return Ok(end); //TODO:?
            }
        
            end = start 
        }
        Err(AppError::new_kind(AppKind::NotExistLineFeed, "Not Exist line feed in file"))
    }
    fn rfind_lines_pos(&self, buffer: &[u8],  lines: &mut u64) -> Option<usize>{
        let res = (0..buffer.len()).rev().try_for_each(|i|  {
            if buffer[i] == 0x0a {
                *lines -= 1;
                println!("lines: {lines}, value:{i}");
                if *lines == 0 {
                    return ControlFlow::Break(i)
                }
            }
            return ControlFlow::Continue(())
        });
        if let ControlFlow::Break(i) = res {
            return Some(i);
        }
        return None;
    }
}

impl TailFollow {
    fn process(&mut self, _channel: &mut Channel) -> crate::error::Result<()>{
        if self.show_lines > 0 {
            self.fsize = TailFollow::get_file_size(&self.fname)?;
            let _pos = self.find_position()?;
            //let _ = self.read_all_lines(pos, channel)?;
        }
        Ok(())
    }
}










#[derive(Debug,Default)]
struct ConsoleSink {
}

impl ConsoleSink {
    fn process(&self, channel:&mut Channel) -> crate::error::Result<()>{
        println!("sink output:");
        while let Some(e) = channel.pop() {
            print!("{}", e);
        }
        Ok(())
    }
}

#[derive(Default, Debug)]
struct TailAgent {
    channel: Channel,
    source: TailSource,
    sink: ConsoleSink,
}
impl TailAgent {
    fn new(fname: &str, lines: u64) -> Self {
        Self {
            channel: Channel::default(),
            source: TailSource::new(fname, lines),
            sink: ConsoleSink::default(),
        }
    }
}
impl Processor for TailAgent {

    fn process(&mut self) -> crate::error::Result<()> {
        self.source.process(&mut self.channel)?;
        self.sink.process(&mut self.channel)?;
        println!("process from agent");
        Ok(())
    }
}




#[cfg(test)]
mod test {
    use super::*;


    #[test]
    fn test_agent(){
        //let fname = "/home/chenglun/dev/plant/src/tail/src/main.rs";
        let mut agent = TailAgent::new("./src/main.rs", 1);
        let _ = agent.process();

        let mut agent = TailAgent::new("./src/main.rs", 0);
        let _ = agent.process();
    }

    #[test]
    fn test_for_rev(){
        for i in (0..10).rev(){
            println!("{i}");
        }
    }

    #[test]
    fn test_main(){
        let mut tail = TailFollow::new("./src/main.rs", 10);
        let mut channel = Channel::default();
        let _ = tail.process(&mut channel);
    }
}
